#include "AbstractEnvironment.h"
#include "parameters.h"
#include "SimpleSelectionStrategy.h"
#include "Generation.h"
#include "Chromosome.h"
#include "AbstractChromosomeFactory.h"

#include<QTime>
#include<time.h>//TODO needed in windows



AbstractEnvironment::AbstractEnvironment(int _populationSize) : QThread(),
                        selectionStrategy(NULL),
                        chromosomeFactory(NULL),
                        solution(NULL),
                        currentGeneration(NULL),
                        crossoverProb(DEFAULT_CROSSOVER_PROB),
                        mutationProb(DEFAULT_MUTATION_PROB),
                        additionalOperatorProb1(0.0),
                        additionalOperatorProb2(0.0),
                        abortWorseGenerations(false),
                        replaceWorstRepresentative(false),
                        addFreshChromosomesCount(0),
                        removeDuplicatesMethod(None),
                        maxGenerationsCount(1),
                        bestFitness(0.0),
                        timeElapsedSec(0.0),
                        populationSize(_populationSize),
                        age(0),
                        abortedGenerationsCount(0),
                        performAdjustFitness(false),
                        finishConditionType(GenerationCount),
                        forceStop(false),
                        emitFrequency(1)
{
    TRACE_FUNC_ENTRY

    qsrand(time(NULL));
    setSelectionStrategy(new SimpleSelectionStrategy());

    TRACE_FUNC_EXIT
}

AbstractEnvironment::~AbstractEnvironment()
{
    TRACE_FUNC_ENTRY
    if(solution)
        delete solution;
    if(chromosomeFactory)
        delete chromosomeFactory;
    if(selectionStrategy)
        delete selectionStrategy;
    if(currentGeneration)
        delete currentGeneration;

    TRACE_FUNC_EXIT
}

void AbstractEnvironment::createPopulation()
{
    TRACE_FUNC_ENTRY
    
    if(currentGeneration)
        delete currentGeneration;

    currentGeneration = new Generation(populationSize, chromosomeFactory);

    age = 0;
    abortedGenerationsCount = 0;

    TRACE_FUNC_EXIT
}

void AbstractEnvironment::createPrimaryGenaration()
{
    createPopulation();

    countFitness(*currentGeneration);

    if(solution)
        delete solution;

    forceStop = false;

    solution = currentGeneration->getBestRepresentative()->duplicate();
    #ifndef NOT_EMIT_SIGNALS
        emit betterChromosomeFound(solution->data());
    #endif
}

void AbstractEnvironment::setSelectionStrategy(SelectionStrategy* _selectionStrategy)
{
    TRACE_FUNC_ENTRY

    if(selectionStrategy == _selectionStrategy)
        return;

    if(selectionStrategy != NULL)
    {
        delete selectionStrategy;
        selectionStrategy = NULL;
    }
    selectionStrategy = _selectionStrategy;

    TRACE_FUNC_EXIT
}

void AbstractEnvironment::setCrossoverProb(double _crossoverProb)
{
    TRACE_FUNC
    crossoverProb = _crossoverProb;
}

void AbstractEnvironment::setMutationProb(double _mutationProb)
{
    TRACE_FUNC
    mutationProb = _mutationProb;
}

void AbstractEnvironment::setAdditionalOperatorProb1(double _additionalOperatorProb1)
{
    TRACE_FUNC
    additionalOperatorProb1 = _additionalOperatorProb1;
}

void AbstractEnvironment::setAdditionalOperatorProb2(double _additionalOperatorProb2)
{
    TRACE_FUNC
    additionalOperatorProb2 = _additionalOperatorProb2;
}

void AbstractEnvironment::setAbortWorseGenerations(bool _abortWorseGenerations)
{
    TRACE_FUNC
    abortWorseGenerations = _abortWorseGenerations;
}

void AbstractEnvironment::setReplaceWorstRepresentative(bool _replaceWorstRepresentative)
{
    TRACE_FUNC
    replaceWorstRepresentative = _replaceWorstRepresentative;
}

void AbstractEnvironment::setAddFreshChromosomes(int _addFreshChromosomes)
{
    TRACE_FUNC
    addFreshChromosomesCount = _addFreshChromosomes;
}

void AbstractEnvironment::setRemoveDuplicatesMethod(int _removeDuplicatesMethod)
{
    TRACE_FUNC
    removeDuplicatesMethod = _removeDuplicatesMethod;
}

void AbstractEnvironment::setMaxGenerationsCount(int _maxGenerationsCount)
{
    maxGenerationsCount = _maxGenerationsCount;
}

void AbstractEnvironment::setAdjustFitness(bool _adjustFitness)
{
    performAdjustFitness = _adjustFitness;
}

void AbstractEnvironment::setFinishConditionType(int _finishConditionType)
{
    finishConditionType = _finishConditionType;
}

void AbstractEnvironment::run()
{
    startSimulation();
}

void AbstractEnvironment::startSimulation()
{
    TRACE_FUNC_ENTRY

    createPrimaryGenaration();

    QTime timer;
    timer.start();

    #ifdef LOG_TO_FILE
        startLogging();
    #endif

    qDebug() << *this << endl;

    while(!finishCondition() && !forceStop)
    {
        //TODO
        selectionStrategy->resetTempData();

        Generation* newGeneration = performReproduction();

        countFitness(*newGeneration);

        if(*solution < *newGeneration->getBestRepresentative())
        {
            delete solution;
            solution = newGeneration->getBestRepresentative()->duplicate();
            #ifndef NOT_EMIT_SIGNALS
                //qDebug() << "Better solution found";
                emit betterChromosomeFound(solution->data());
            #endif
        }

        if(abortWorseGenerations && newGeneration->totalFitness() < currentGeneration->totalFitness())
        {
            abortedGenerationsCount++;
            delete newGeneration;
            continue;//abort new generation when it is worse than current
        }

        //if new generation best is worst than best ever replace worst one
        if(replaceWorstRepresentative && *newGeneration->getBestRepresentative() < *solution)
        {
            newGeneration->replaceChromosome(newGeneration->size()-1, solution->duplicate());
            //qDebug() << "replaceWorst with: " << solution->toString();
        }

        if(addFreshChromosomesCount > 0)
            addFreshChromosomes(*newGeneration);

        if(removeDuplicatesMethod != None)
        {
            //qDebug() << *newGeneration << endl;
            //qDebug() << "d1:" << *newGeneration.findDuplicates().size();
            removeDuplicates(*newGeneration);
            //qDebug() << "d2: " << *newGeneration.findDuplicates().size();
        }

        replaceGeneration(newGeneration);
        qDebug() << "population age:" << getAge();
        //qDebug() << *this << endl;

        #ifndef NOT_EMIT_SIGNALS
            if(age % emitFrequency == 0)//emmiting is slow
            {
                emit simulationInfo(populationSize, age, currentGeneration->maxFitness(),
                                    currentGeneration->minFitness(),
                                    currentGeneration->totalFitness(),timer.elapsed()/1000.0);
            }
        #endif

        #ifdef LOG_TO_FILE
            logInfo();
        #endif


    }

    #ifndef NOT_EMIT_SIGNALS
            emit simulationInfo(populationSize, age, currentGeneration->maxFitness(),
                                currentGeneration->minFitness(),
                                currentGeneration->totalFitness(),timer.elapsed()/1000.0);
    #endif

    #ifdef LOG_TO_FILE
        finishLogging();
    #endif

    timeElapsedSec = timer.elapsed()/1000.0;

    qDebug() << "solution found at: " << getAge();
    qDebug() << "generations lost: " << getAbortedGenerationsCount();

    TRACE_FUNC_EXIT
}

void AbstractEnvironment::removeDuplicates(Generation& generation)
{
    QList<int> duplList = generation.findDuplicates();
    //qDebug() << "found " << duplList.size() << "duplicates: " << duplList;
    switch(removeDuplicatesMethod)
    {
        case None:
            break;
        case Mutate:
            //qDebug() << "mutate duplicates";

            for(int i=0;i<duplList.size();++i)
            {
                generation[duplList.at(i)].mutate();
                fitnessFunction(generation[duplList.at(i)]);
            }
            break;
        case Replace:
            {
            //qDebug() << "replace duplicates";

            for(int i=0;i<duplList.size();++i)
            {
                Chromosome* freshChromosome = chromosomeFactory->getNewRandomChromosome();
                fitnessFunction(*freshChromosome);
                generation.replaceChromosome(duplList.at(i), freshChromosome);
                //qDebug() << "addFresh: " << *freshChromosome;
            }
            }
            break;
        default:
            break;
    }
    generation.sort();
}

void AbstractEnvironment::addFreshChromosomes(Generation& generation)
{
    for(int i=0;i<addFreshChromosomesCount;++i)
    {
        Chromosome* freshChromosome = chromosomeFactory->getNewRandomChromosome();
        fitnessFunction(*freshChromosome);
        generation.replaceChromosome(generation.size()-1-i, freshChromosome);
        //qDebug() << "addFresh: " << *freshChromosome;
    }
}

void AbstractEnvironment::countFitness(Generation& generation)
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<generation.size();++i)
        fitnessFunction(generation[i]);
    generation.sort();
    TRACE_FUNC_EXIT
}

void AbstractEnvironment::adjustFitness(Generation& generation)
{
    double minFitness = generation.minFitness();

    double prefferedMin = 0;//TODO add small value to avoid 0 fitness?
    for(int i=0;i<generation.size();++i)
        generation[i].setFitness(generation[i].getFitness() - minFitness + prefferedMin);

}

//algorithm from Goldberg
void AbstractEnvironment::scaleFitness(Generation& generation)
{
    //double fitMul = 2.0;
    //double delta;

    double valA = 2.0;
    double valB = 0.0;

   /* double min = generation.minFitness();
    double max = generation.maxFitness();
    double avg = generation.totalFitness()/generation.size();

    if(min > (fitMul*avg - max) / (fitMul - 1.0))
    {
        qDebug() << "AbstractEnvironment::scaleFitness 1";
        delta = max - avg;
        valA = (fitMul - 1.0)*avg / delta;
        valB = avg * (max - fitMul*avg) / delta;
    }
    else
    {
        qDebug() << "AbstractEnvironment::scaleFitness 1";
        delta = avg - min;
        valA = avg/delta;
        valB = -min * avg / delta;
    }*/

    qDebug() << "A: " << valA << "B: " << valB;

    for(int i=0;i<generation.size();++i)
        generation[i].setFitness(valA * generation[i].getFitness() + valB);

}

bool AbstractEnvironment::finishCondition() const
{
    TRACE_FUNC

    switch(finishConditionType)
    {
        //if we know best fitness, useful for testing
        case BestFitnessKnown:
            return bestChromosomeFound();
        case GenerationCount:
        default:
            return (getAge() >= maxGenerationsCount);
    }
}

Chromosome* AbstractEnvironment::getSolution() const
{
    TRACE_FUNC
    return solution;
}

double AbstractEnvironment::getWorkingTimeSec() const
{
    return timeElapsedSec;
}

QString AbstractEnvironment::decodeChromosome(const Chromosome& chrom) const
{
    return chrom.toString();
}

QString AbstractEnvironment::getStatisticsString() const
{
    QString str("");
    str += "age: " + QString::number(getAge()) + "\n";
    str += decodeChromosome(*getSolution());
    str += "time: " +  QString::number(getWorkingTimeSec(), 'g') + "\n";
    str += "generations/second: " +  QString::number(getAge()/getWorkingTimeSec(), 'g') + "\n";
    str += "fitness: " + QString::number(getSolution()->getFitness(),'f',20);
    return str;
}

void AbstractEnvironment::setBestFitness(double _bestFitness)
{
    bestFitness = _bestFitness;
}

void AbstractEnvironment::setPopulationSize(int _populationSize)
{
    populationSize = _populationSize;
}

void AbstractEnvironment::setEmitFrequency(int _emitFrequency)
{
    emitFrequency = _emitFrequency;
}

void AbstractEnvironment::setMinimizeFitness(bool _minimize)
{
    Chromosome::minimize = _minimize;
}

bool AbstractEnvironment::bestChromosomeFound() const
{
    //TODO check if best fitness is set
    return currentGeneration->getBestRepresentative()->getFitness() >= bestFitness;
}

int AbstractEnvironment::getAge() const
{
    TRACE_FUNC
    return age;
}

int AbstractEnvironment::getAbortedGenerationsCount() const
{
    TRACE_FUNC
    return abortedGenerationsCount;
}

Generation* AbstractEnvironment::performReproduction()
{
    TRACE_FUNC_ENTRY
    Generation* newGeneration = new Generation();

    if(performAdjustFitness)
    {
        //qDebug() << "BEFORE ADJUST: " << *currentGeneration;
        adjustFitness(*currentGeneration);
        //qDebug() << "AFTER ADJUST: " << *currentGeneration;
        //if(1)//TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //    scaleFitness(currentGeneration);
        //qDebug() << "AFTER SCALE: " << *currentGeneration;
    }

    while(newGeneration->size() < populationSize)
    {
        Chromosome* toAdd = currentGeneration->getProcreator(selectionStrategy);
        newGeneration->addChromosome(toAdd->duplicate());
    }

    newGeneration->performCrossOver(crossoverProb);
    newGeneration->performMutation(mutationProb);

    if(additionalOperatorProb1 + additionalOperatorProb2 > 0)
        newGeneration->performAdditionalOperators(additionalOperatorProb1, additionalOperatorProb2);

    TRACE_FUNC_EXIT
    return newGeneration;
}

void AbstractEnvironment::replaceGeneration(Generation* newGeneration)
{
    TRACE_FUNC_ENTRY
    if(currentGeneration)
        delete currentGeneration;
    currentGeneration = newGeneration;
    age++;
    TRACE_FUNC_EXIT
}

QDebug operator<<(QDebug dbg, const AbstractEnvironment &env)
{
    TRACE_FUNC_ENTRY
    dbg.nospace() << ">>env - population age: " << env.getAge() << " abortedGenerations: " << env.getAbortedGenerationsCount() << endl;
    dbg.nospace() << *env.currentGeneration;
    dbg.nospace() << "<<env population";
    TRACE_FUNC_EXIT
    return dbg.maybeSpace();
}

void AbstractEnvironment::stop()
{
    qDebug() << "AbstractEnvironment::stop()!!!!!!!!!";
    forceStop = true;
}

#ifdef LOG_TO_FILE
void AbstractEnvironment::startLogging()
{
    QString fileName = "simulation_" + QDateTime::currentDateTime().toString("yyyy-MM-dd_hh-mm-ss") + ".dat";
    QString gplotFileName = "temp.gplot";
    QFile* gplotFile = new QFile (gplotFileName);
    if ( !gplotFile->open ( QIODevice::WriteOnly | QIODevice::Text ) )
        qFatal("cannot open file");
    QTextStream gplotStream(gplotFile);
    gplotStream << "set log y" << endl;
    gplotStream << "set key below" << endl;
    gplotStream << "set xlabel \"numer generacji\"" << endl;
    gplotStream << "set ylabel \"roznica\"" << endl;
    gplotStream << "set terminal postscript eps color" << endl;
    gplotStream << "set output \"" << "temp.eps\"" << endl;
    gplotStream << "plot \"" << fileName << "\" using 1:6 title \"best Fit\" with linespoints,\\" << endl;
    gplotStream << "\"" << fileName << "\" using 1:8 title \"best ever Fit\" with lines" << endl;
    gplotFile->close();
    delete gplotFile;

    file = new QFile (fileName );

    if ( !file->open ( QIODevice::Append | QIODevice::Text ) )
        qFatal("cannot open file");

    stream = new QTextStream(file);
    *stream << "#age\tminFit\tmaxFit\tavFit\ttotFit\tdiff\tbestEver\tdiffBestEver" << endl;
}

void AbstractEnvironment::logInfo()
{
    *stream << age << "\t";
    *stream << currentGeneration->maxFitness() << "\t";
    *stream << currentGeneration->minFitness() << "\t";
    *stream << currentGeneration->totalFitness()/populationSize << "\t";
    *stream << currentGeneration->totalFitness() << "\t";
    *stream << qAbs(currentGeneration->maxFitness() - bestFitness) << "\t";
    *stream << solution->getFitness() << "\t";
    *stream << qAbs(solution->getFitness() - bestFitness);
    *stream << endl;
}

void AbstractEnvironment::finishLogging()
{
    file->close();
    delete file;
    delete stream;

    if (system("gnuplot temp.gplot") < 0)
        qWarning("Error calling gnuplot");
}

#endif
